#pragma once

#include <type_traits>

namespace zenoui {

// https://softwareengineering.stackexchange.com/questions/194412/using-scoped-enums-for-bit-flags-in-c
template <typename Enum>
class BitFlag {
    static_assert(std::is_enum_v<Enum>, "BitFlag: given template parameter is not an enum");
public:
/* #if __cplusplus >= 202002L */
/*     using enum Enum; */
/* #endif */
    constexpr BitFlag() = default;
    constexpr BitFlag(Enum value) : bits(static_cast<typename std::underlying_type<Enum>::type>(value)) {}

    constexpr BitFlag operator|(Enum value) const { BitFlag result = *this; result.bits |= tounderlie(value); return result; }
    constexpr BitFlag operator&(Enum value) const { BitFlag result = *this; result.bits &= tounderlie(value); return result; }
    constexpr BitFlag operator^(Enum value) const { BitFlag result = *this; result.bits ^= tounderlie(value); return result; }
    constexpr BitFlag operator~() const { BitFlag result = *this; result.bits = ~result.bits; return result; }

    constexpr BitFlag& operator|=(Enum value) { bits |= tounderlie(value); return *this; }
    constexpr BitFlag& operator&=(Enum value) { bits &= tounderlie(value); return *this; }
    constexpr BitFlag& operator^=(Enum value) { bits ^= tounderlie(value); return *this; }

    constexpr explicit operator bool() const { return (bool)bits; }
    constexpr operator Enum() const { return static_cast<Enum>(static_cast<typename std::underlying_type<Enum>::type>(bits)); }

private:
    typename std::underlying_type<Enum>::type bits{};

    static constexpr typename std::underlying_type<Enum>::type tounderlie(Enum value) {
        return static_cast<typename std::underlying_type<Enum>::type>(value);
    }
};

}
